// Author	: damphat@gmail.com
// Created	: 01/12/2008

#pragma once
#include "vn/IOCommon.h"
#include "vn/Path.h"
#include "vn/Stream.h"
#include "vn/IOException.h"

#include <io.h>
#include <errno.h>
#include <fcntl.h>
#include <share.h>
#include <sys/types.h>
#include <sys/stat.h>

#define CHECK_IO_LOW_API(condition){\
	if(!(condition)) {\
		char ermes[256];\
		wsprintfA(ermes, "%s\n%s", __FUNCTION__, strerror(errno));\
		IOException e(GetLastError(), ermes);\
		throw e;\
	}\
}

IO_BEGIN

// [enum]
BEGIN_ENUM(FileMode)
	CreateNew		= 1,
	Create			= 2,
	Open			= 3,
	OpenOrCreate	= 4,
	Truncate		= 5,
	Append			= 6
END_ENUM

// Flags
BEGIN_ENUM(FileAccess)
	Read		= 1,
	Write		= 2,
	ReadWrite	= Read | Write
END_ENUM

// Flags
BEGIN_ENUM(FileShare)
	None			= 0,
	Read			= 1,
	Write			= 2,
	ReadWrite		= Read | Write
END_ENUM

// Flags
BEGIN_ENUM(FileOptions)
	None			= 0x00000000,
	DeleteOnClose	= 0x04000000,
	SequentialScan	= 0x08000000,
	RandomAccess	= 0x10000000
END_ENUM


class FileStream : public Stream {
public:
	FileStream()
		: m_fd(0)
		, m_canRead(false)
		, m_canWrite(false)
	{ }

	void Open(String path, FileMode mode) {
		FileAccess access;

		if (mode == FileMode::Append)
			access = FileAccess::Write;
		else
			access = FileAccess::ReadWrite;

		Open(path, mode, access, FileShare::Read, FileOptions::None);
	}

	void Open(String path, FileMode mode, FileAccess access) {
		Open(path, mode, access, FileShare::Read, FileOptions::None);
	}

	void Open(String path, FileMode mode, FileAccess access, FileShare share) {
		Open(path, mode, access, share, FileOptions::None);
	}

	void Open(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options) {
		CHECK_ARG_PATH(path);
		CHECK_STATUS(m_fd == 0);

		int oflag = _O_BINARY;
		int shflag = 0;
		int pmode =  _S_IREAD | _S_IWRITE;

		switch(mode) {
			case FileMode::CreateNew	: oflag |= (_O_CREAT | _O_EXCL);	break;
			case FileMode::Create		: oflag |= (_O_CREAT | _O_TRUNC);	break;
			case FileMode::Open			: oflag |= (0);	/*_O_EXCL?*/		break;
			case FileMode::OpenOrCreate	: oflag |= (_O_CREAT);				break;
			case FileMode::Truncate		: oflag |= (_O_TRUNC);				break;
			case FileMode::Append		: oflag |= (_O_APPEND);				break;
		}

		switch(access) {
			case FileAccess::Read		: oflag |= (_O_RDONLY);				break;
			case FileAccess::Write		: oflag |= (_O_WRONLY);				break;
			case FileAccess::ReadWrite	: oflag |= (_O_RDWR);				break;
		}

		switch(share) {
			case FileShare::None		: shflag |= (_SH_DENYRW);			break;
			case FileShare::Read		: shflag |= (_SH_DENYWR);			break;
			case FileShare::Write		: shflag |= (_SH_DENYRD);			break;
			case FileShare::ReadWrite	: shflag |= (_SH_DENYNO);			break;
		}

		if(FileOptions::None			& options)	oflag |= (0);
		if(FileOptions::DeleteOnClose	& options)	oflag |= (_O_TEMPORARY);
		if(FileOptions::SequentialScan	& options)	oflag |= (_O_SEQUENTIAL);
		if(FileOptions::RandomAccess	& options)	oflag |= (_O_RANDOM);



		int tmp_fd;
#if _UNICODE
		// errno_t er = _wsopen_s(&tmpfile, path.C_STR(), oflag, shflag, pmode);
		tmp_fd = _wsopen(path.C_STR(), oflag, shflag, pmode);
#else
		//errno_t er = _sopen_s(&tmpfile, path.C_STR(), oflag, shflag, pmode);
		tmp_fd = _sopen(path.C_STR(), oflag, shflag, pmode);
#endif
		CHECK_IO_LOW_API(tmp_fd != -1);
		m_fd = tmp_fd;

		if (oflag & _O_RDWR) {
			m_canRead	= true;
			m_canWrite	= true;
		}
		else {
			m_canWrite	= (oflag & _O_WRONLY);
			m_canRead	= !m_canWrite;
		}
	}


	// you don't need loop for reading from file, may return (<len)
	// buffer is limit to int
	// return value of text file will assume CR-LF as 1 byte
	virtual StmLen Read(Buf8 *buf, StmLen len) {
		CHECK_ARG_BUFFER(buf, len);
		CHECK_ARG_RANGE(len, 0, UINT_MAX);	// see _read
		CHECK_STATUS(m_fd);
		int nread = _read(m_fd, buf, (UInt)len);
		CHECK_IO_LOW_API(nread != -1);
		return nread;
	}

	virtual void Write(const Buf8 *buf, StmLen len) {
		CHECK_ARG_BUFFER(buf, len);
		CHECK_ARG_RANGE(len, 0, UINT_MAX);	// see _write
		CHECK_STATUS(m_fd);
		int nwrite = _write(m_fd, buf, (UInt)len);
		// return value will be equal to 'len' equal to -1
		CHECK_IO_LOW_API(nwrite != -1);
	}

	virtual StmPos Seek(StmOff offset, SeekOrigin origin) {
		CHECK_STATUS(m_fd);
		Int64 new_pos;
		switch(origin) {
		case SeekBegin:
			new_pos = _lseeki64(m_fd, offset, SEEK_SET);
			break;
		case SeekCurrent:
			new_pos = _lseeki64(m_fd, offset, SEEK_CUR);
			break;
		case SeekEnd:
			new_pos = _lseeki64(m_fd, offset, SEEK_END);
			break;
		}
		CHECK_IO_LOW_API(new_pos != -1);
		return new_pos;
	}

	virtual void Flush() {
		CHECK_STATUS(m_fd);
		int ret = _commit(m_fd);
		CHECK_IO_LOW_API(ret != -1);
	}

	virtual void Close() {
		if(m_fd) {
			int iret = _close(m_fd);
			CHECK_IO_LOW_API(iret != -1);
			m_fd = 0;
		}
	}

	virtual bool CanRead()	{ return m_canRead; }
	virtual bool CanWrite()	{ return m_canWrite; }
	virtual bool CanSeek()	{ return true; }

	virtual StmPos GetPosition() {
		CHECK_STATUS(m_fd);
		__int64 cur_pos = _telli64(m_fd);
		CHECK_IO_LOW_API(cur_pos != -1);
		return cur_pos;
	}

	virtual void SetPosition(StmPos pos) {
		CHECK_ARG(pos >= 0);
		CHECK_STATUS(m_fd);
		__int64 new_pos;
		new_pos = _lseeki64(m_fd, pos, SEEK_SET);
		CHECK_IO_LOW_API(new_pos != -1);
	}

	virtual StmLen GetLength() {
		CHECK_STATUS(m_fd);
		__int64 file_len = _filelengthi64(m_fd);
		CHECK_IO_LOW_API(file_len != -1);
		return file_len;
	}

	virtual void SetLength(StmLen len) {
		CHECK_ARG(len >= 0);
		CHECK_STATUS(m_fd);
		// SetPosition(len);
		//errno_t er = _chsize_s(m_fd, len);
		// TODO: how mingw can change file size 64
		CHECK_ARG_RANGE(len, 0, LONG_MAX);
		int er = _chsize(m_fd, len);
		CHECK_IO_LOW_API(er != -1);
	}

	virtual ~FileStream() {
		Close();
	}

public:
	int		m_fd;
	bool	m_canRead;
	bool	m_canWrite;
};

IO_END

// 1. Should FileStream be shareable, FileStream a("help.txt"); b(a);
// 2. lock
// 3. parse open param to determine canread, canwrite
// 4. file attribute
// 5. test with large file from CD
// 6. Linux
// 7. private data
// 8. path as String, compatible with unicode and ansi
// 9. __int64 to Int64
// 10.Network file?
// 11.HANDLE CreateFileMapping
// 12.Should we split FileStream/FileObject like NetworkStream/Socket?
// 13.Asyn
// 14.Exeption & message
// 15.More tests
// 16.Requirement collections


// SHOULD WE LOCKRANGE BEFORE WRITE OPERATION?
// ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.WIN32COM.v10.en/fileio/fs/appending_one_file_to_another_file.htm
// Append the first file to the end of the second file.
// Lock the second file to prevent another process from
// accessing it while writing to it. Unlock the
// file when writing is finished.
//
//do
//{
//	if (ReadFile(hFile, buff, sizeof(buff), &dwBytesRead, NULL))
//	{
//		dwPos = SetFilePointer(hAppend, 0, NULL, FILE_END);
//		LockFile(hAppend, dwPos, 0, dwBytesRead, 0);
//		WriteFile(hAppend, buff, dwBytesRead, &dwBytesWritten, NULL);
//		UnlockFile(hAppend, dwPos, 0, dwBytesRead, 0);
//	}
//} while (dwBytesRead == sizeof(buff));

